package algo.math.evaluator.context.operator;

import java.lang.reflect.Type;
import algo.math.evaluator.base.IBase;
import algo.math.evaluator.base.exceptions.InvalidToken;
import algo.math.evaluator.base.exceptions.UnrecognizedToken;
import algo.math.evaluator.context.EvaluationContext;
import algo.math.evaluator.context.operator.interfaces.IOperator;
import algo.math.evaluator.exceptions.InvalidOperationException;
import algo.math.evaluator.value.IValue;
import algo.math.evaluator.value.Value;

/**
 * Represent an arithmetic division.
 */
public  class Division extends OperatorPrimitive {

	private String _name = OperatorSymbol.DIVIDE.toString();
	
	private String _value= "/";
	
	private TokenType _type=TokenType.OPERATOR;
	
	private int _level=3;
	
	private int _nbOperands =2;
	
	private final int MAX_PRECISION = 30;

	/* (non-Javadoc)
	 * @see algo.math.evaluator.context.operator.interfaces.IOperator#getName()
	 */
	public String getName() {
		return _name;
	}

	/* (non-Javadoc)
	 * @see algo.math.evaluator.context.operator.interfaces.IOperator#getLevel()
	 */
	public int getLevel() {
		return _level;
	}

	
	/* (non-Javadoc)
	 * @see algo.math.evaluator.context.operator.interfaces.IOperator#setLevel(int)
	 */
	@Override
	public void setLevel(int level) {
		return;
	}

	
	/* (non-Javadoc)
	 * @see algo.math.evaluator.tokenizer.interfaces.IToken#getType()
	 */
	public TokenType getType() {
		return _type;
	}

	
	/* (non-Javadoc)
	 * @see algo.math.evaluator.tokenizer.interfaces.IToken#setType(algo.math.evaluator.tokenizer.interfaces.IToken.TokenType)
	 */
	public void setType(TokenType type) {
		return;
	}
	
	/* (non-Javadoc)
	 * @see algo.math.evaluator.tokenizer.interfaces.IToken#setValue(java.lang.String)
	 */
	public void setValue(String value) {
		return;
	}

	
	/* (non-Javadoc)
	 * @see algo.math.evaluator.context.operator.interfaces.IOperator#getValue()
	 */
	public String getValue() {
		return _value;
	}

	
	/* (non-Javadoc)
	 * @see algo.math.evaluator.context.IComponentObject#getComponentType()
	 */
	public Type getComponentType() {
			return IOperator.class;
	}

	
	/* (non-Javadoc)
	 * @see algo.math.evaluator.context.IComponentObject#getComponentId()
	 */
	public String getComponentId() {
			return _value;
	}

	
	/* (non-Javadoc)
	 * @see algo.math.evaluator.context.IComponentObject#getComponentName()
	 */
	public String getComponentName() {
			return _name;
	}
	
	/* (non-Javadoc)
	 * @see algo.math.evaluator.context.operator.interfaces.IOperator#getNbOperands()
	 */
	@Override
	public int getNbOperands() {
		return _nbOperands;
	}


	/* (non-Javadoc)
	 * @see algo.math.evaluator.context.operator.interfaces.IOperator#evaluate(algo.math.evaluator.value.IValue[])
	 */
	@Override
	public IValue evaluate(IValue[] operands) throws UnrecognizedToken, InvalidToken, InvalidOperationException {
		return resolve (operands);
	}


	private String[] _div (IValue n1, IValue n2,IBase b) throws UnrecognizedToken, InvalidToken
	{
		
		String [] result = new String[2];
		IValue b10 = new Value();
		b10.setValue("10", b);
		Multiplication m = new Multiplication();
		IValue checkValue = m.evaluate(new IValue[]{n2,b10});
		
		long i =0;
		
		while (n1.compareTo(checkValue)==IValue.Compare.SUPERIOR)
		{
			n2= checkValue;
			 m = new Multiplication();
			checkValue=m.evaluate(new IValue[]{checkValue,b10});
			i+=10;
		}
		
		
		
		while (
				n1.compareTo(n2)==IValue.Compare.SUPERIOR ||
				n1.compareTo(n2)==IValue.Compare.EQUALS)
		{
			Substraction s = new Substraction();
			IValue gg = s.evaluate(new IValue[]{n1, n2});
			n1 = gg;
			i+=1;
		}
			result[0]=b.valueOf(i);
			
			
			
		 result[1] = n1.getIntegerValue();
		 return result;
	}


	private Object[] _prepare(IValue nb1, IValue nb2, IBase b) throws UnrecognizedToken
	{
		
		int bufferDifN1_N2 =  nb1.getIntegerValue().length()-nb2.getIntegerValue().length();
		IValue dividend = new Value();
		dividend.setValue(nb1.getIntegerValue()+nb1.getDecimalValue(), b);
		IValue divisor = new Value();
		divisor.setValue(nb2.getIntegerValue()+nb2.getDecimalValue(),b);
		
		int buffer = 0;
		
		if (dividend.getIntegerValue().length()>divisor.getIntegerValue().length())
		{
			StringBuffer bufferedVal = new StringBuffer ();
			StringBuffer val = new StringBuffer ();
			bufferedVal.append(divisor.getIntegerValue());
			
			 buffer =  dividend.getIntegerValue().length()-divisor.getIntegerValue().length();
			
			for (long i = 0; i<buffer;i++ )
			{
				bufferedVal.append(b.valueOf(0));
				val.append(b.valueOf(0));
			}
			
			divisor.setValue(bufferedVal.toString(), b);
			
		}
		
		else if (dividend.getIntegerValue().length()<divisor.getIntegerValue().length())
		{
			
			StringBuffer bufferedVal = new StringBuffer ();
			StringBuffer val = new StringBuffer ();
			bufferedVal.append(dividend.getIntegerValue());
			
			 buffer =  divisor.getIntegerValue().length()-dividend.getIntegerValue().length();
			
			for (long i = 0; i<buffer;i++ )
			{
				bufferedVal.append(b.valueOf(0));
				val.append(b.valueOf(0));
			}
			
			dividend.setValue(bufferedVal.toString(), b);
			
		}
		
		return new Object [] {dividend, divisor, bufferDifN1_N2};
	}
	
	/* (non-Javadoc)
	 * @see algo.math.evaluator.context.operator.OperatorPrimitive#resolve(algo.math.evaluator.value.IValue[])
	 */
	@Override
	protected IValue resolve(IValue[] operands) throws UnrecognizedToken, InvalidToken, InvalidOperationException {
		
		if (operands[1].isZero())
			throw new InvalidOperationException("Division by zero cannot be resolved");
		
		IBase b = EvaluationContext.GetInstance().getBase();
		StringBuffer intPart = new StringBuffer();
		StringBuffer decPart = new StringBuffer();
		
		
		 if  (operands[0].compareTo(operands[1])== IValue.Compare.EQUALS)
			{
				IValue nb = new Value ();
				nb.setValue(b.valueOf(1), b);
				return  nb;
			}
		
		Object [] ops = _prepare (operands[0], operands[1],b);
		IValue dividend = (IValue) ops[0];
		IValue divisor = (IValue) ops[1];
		int buffer = (Integer) ops[2];
		String [] result = _div(dividend,divisor,b);
		
		intPart.append(result[0]);
		
		if (b.valueOf(result[1])>0)
		{
		IValue res= new Value();
		IValue rem= new Value();
		IValue rem2= new Value();
		
		res.setValue(result[0],b);
		rem.setValue(result[1],b);
		rem2.setValue("10", b);
		int loop =0;
		
		Multiplication m = new Multiplication();
		
		while (loop< MAX_PRECISION)
		{
			result = _div(m.evaluate(new IValue[]{rem,rem2}), divisor,b);
			decPart.append(result[0]);
			rem.setValue(result[1], b);
				if (rem.isZero())
					break;
			loop++;
		}
		
		}
		
		IValue ret = new Value ();
		
		String r,r2;
		
		if (decPart.toString().equals(""))
		{
		 if ((int)buffer>0)
			{
				
				for (int i =0; i< (int)buffer;i++)
				{
					decPart.append(b.minValue());
				}
		
			
			 r= (intPart.toString()+decPart.toString()).substring(0,intPart.length()+ (int) buffer);
			 r2= (intPart.toString()+decPart.toString()).substring(intPart.length()+ (int) buffer,(intPart.toString()+decPart.toString()).length());
			 ret.setValue(r+'.'+r2, b);
				
			 if (operands[0].isNegative()|| operands[1].isNegative())
					ret.SetIsNegative(true);
			
			 return ret;
			
			}
			else
			{
				for (int i =(int)buffer; i< 0;i++)
				{
					decPart.append(b.minValue());
				}
				
				buffer = ((int) buffer)-((int) buffer)*2;
				 r= (decPart+intPart.toString()).substring(0,intPart.length()+ (int) buffer);
				 r2= (decPart+intPart.toString()).substring(intPart.length()+ (int) buffer,(intPart.toString()+decPart.toString()).length());
				 ret.setValue(r2+'.'+r, b);
					
				 if (operands[0].isNegative()|| operands[1].isNegative())
						ret.SetIsNegative(true);
				
				 return ret;
			}
		}
		
		if (((int) buffer)+intPart.length()<0)
		{
			while (((int) buffer)+intPart.length()<0)
			{
				intPart.insert(0, b.valueOf(0));
			}
			
			r= (intPart.toString()+decPart.toString()).substring(0,intPart.length()+ (int) buffer);
			r2= (intPart.toString()+decPart.toString()).substring(intPart.length()+ (int) buffer,(intPart.toString()+decPart.toString()).length());

		}
		else
		{
			while ((intPart.toString()+decPart.toString()).length()<intPart.length()+ (int) buffer)
			{
				decPart.append(b.valueOf(0));
			}
			
			r= (intPart.toString()+decPart.toString()).substring(0,intPart.length()+ (int) buffer);
			r2= (intPart.toString()+decPart.toString()).substring(intPart.length()+ (int) buffer,(intPart.toString()+decPart.toString()).length());
		}
		
			ret.setValue(r+'.'+r2, b);
			
		 if (operands[0].isNegative()|| operands[1].isNegative())
				ret.SetIsNegative(true);
		
		 return ret;
	}
	
}
